package com.qezhhnjy.cloud.zk.curator.api;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CreateBuilder;
import org.apache.curator.framework.api.DeleteBuilder;
import org.apache.curator.framework.api.GetDataBuilder;
import org.apache.curator.framework.api.SetDataBuilder;
import org.apache.curator.framework.api.transaction.CuratorOp;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * @author zhaoyangfu - 2020/8/19 16:43
 * http://www.throwable.club/2018/12/16/zookeeper-curator-usage/#Zookeeper%E5%AE%A2%E6%88%B7%E7%AB%AFCurator%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3
 */
@Component
@Slf4j
public class CuratorApi {

    @Resource
    private CuratorFramework zkClient;

    /**
     * Zookeeper的节点创建模式：
     * PERSISTENT：持久化
     * PERSISTENT_SEQUENTIAL：持久化并且带序列号
     * EPHEMERAL：临时
     * EPHEMERAL_SEQUENTIAL：临时并且带序列号
     * 通过CreateMode可查看其他创建模式
     */
    public CreateBuilder create() {
        return zkClient.create();
    }

    /**
     * 创建一个节点，初始内容为空
     * 如果没有设置节点属性，节点创建模式默认为持久化节点，内容默认为空
     */
    public void create(String path) throws Exception {
        create().forPath(path);
    }

    /**
     * 创建一个节点，附带初始化内容
     */
    public void create(String path, byte[] data) throws Exception {
        create().forPath(path, data);
    }

    /**
     * 创建一个节点，指定创建模式，内容为空
     */
    public void create(CreateMode mode, String path) throws Exception {
        create().withMode(mode).forPath(path);
    }

    /**
     * 创建一个节点,指定创建模式,附带初始化内容
     */
    public void create(CreateMode mode, String path, byte[] data) throws Exception {
        create().withMode(mode).forPath(path, data);
    }

    /**
     * 自动递归创建所需的父节点
     */
    public void createWithParent(CreateMode mode, String path, byte[] data) throws Exception {
        create().creatingParentContainersIfNeeded()
                .withMode(mode)
                .forPath(path, data);
    }

    public DeleteBuilder delete() {
        return zkClient.delete();
    }

    /**
     * 删除一个节点
     * 此方法只能删除叶子节点,否则会抛出异常.
     */
    public void delete(String path) throws Exception {
        delete().forPath(path);
    }

    /**
     * 删除一个节点并递归删除其所有的子节点.
     */
    public void deleteWithChildren(String path) throws Exception {
        delete().deletingChildrenIfNeeded().forPath(path);
    }

    /**
     * 删除一个节点,强制指定版本进行删除
     */
    public void delete(String path, int version) throws Exception {
        delete().withVersion(version).forPath(path);
    }

    /**
     * 删除一个节点,强制保证删除
     * guaranteed()接口是一个保障措施，只要客户端会话有效，那么Curator会在后台持续进行删除操作，直到删除节点成功。
     */
    public void deleteGuaranteed(String path) throws Exception {
        delete().guaranteed().forPath(path);
    }

    public GetDataBuilder get() {
        return zkClient.getData();
    }

    public byte[] get(String path) throws Exception {
        return get().forPath(path);
    }

    public byte[] getWithStat(String path) throws Exception {
        return get().storingStatIn(new Stat()).forPath(path);
    }

    public SetDataBuilder set() {
        return zkClient.setData();
    }

    public Stat set(String path, byte[] data) throws Exception {
        return set().forPath(path, data);
    }

    /**
     * 更新一个节点的数据内容，强制指定版本进行更新
     */
    public Stat set(String path, byte[] data, int version) throws Exception {
        return set().withVersion(version).forPath(path, data);
    }

    /**
     * 检查节点是否存在
     * 该方法返回一个Stat实例，用于检查ZNode是否存在的操作.
     * 可以调用额外的方法(监控或者后台处理)并在最后调用forPath()指定要操作的ZNode
     */
    public Stat exist(String path) throws Exception {
        return zkClient.checkExists().forPath(path);
    }

    /**
     * 获取某个节点的所有子节点路径
     * 该方法的返回值为List,获得ZNode的子节点Path列表。
     * 可以调用额外的方法(监控、后台处理或者获取状态watch, background or get stat)
     * 并在最后调用forPath()指定要操作的父ZNode
     */
    public List<String> children(String path) throws Exception {
        return zkClient.getChildren().forPath(path);
    }

    /**
     * 事务
     */
    public List<CuratorTransactionResult> transaction() throws Exception {
        List<CuratorOp> ops = new ArrayList<>();
        ops.add(zkClient.transactionOp().check().forPath("/check"));
        ops.add(zkClient.transactionOp().create().forPath("/update"));
        ops.add(zkClient.transactionOp().delete().forPath("/delete"));
        ops.add(zkClient.transactionOp().setData().forPath("/setData"));
        return zkClient.transaction().forOperations(ops);
    }

    /**
     * 上面提到的创建、删除、更新、读取等方法都是同步的，Curator提供异步接口，
     * 引入了BackgroundCallback接口用于处理异步接口调用之后服务端返回的结果信息。
     * BackgroundCallback接口中一个重要的回调值为CuratorEvent，
     * 里面包含事件类型、响应吗和节点的详细信息。
     *
     * 如果inBackground()方法不指定executor，那么会默认使用Curator的EventThread去进行异步处理。
     */
    public void async() throws Exception {
        Executor executor = Executors.newFixedThreadPool(2);
        create().creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .inBackground((curatorFramework, curatorEvent) -> {
                    System.out.println(String.format("eventType:%s,resultCode:%s", curatorEvent.getType(), curatorEvent.getResultCode()));
                }, executor)
                .forPath("/path");
    }
}
